package net.homelinux.chaoswg.io.renga.core.gui;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JComponent;

import net.homelinux.chaoswg.io.renga.core.gui.operations.CompositeOperation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.Control;
import net.homelinux.chaoswg.io.renga.core.gui.operations.FunctionInputOperation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.FunctionOutputOperation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.Operation;
import net.homelinux.chaoswg.io.renga.core.gui.operations.helper.InputOutputListener;
import net.homelinux.chaoswg.io.renga.core.gui.operations.helper.OperationListener;

/* 
 * This shows a composite operation
 * as this is also a member in a Change of responsibility it has nothing much to do 
 */ 
public class DrawingArea extends JComponent implements Observer, InputOutputListener, OperationListener {
    private final DrawingArea _this = this;
    
    private boolean connecting = false;
    private Point startPoint = null;
    private Point currentPoint = null;
    
    //Any composite operation has a control
    private Control control; 

    public DrawingArea (final CompositeOperation cp) {
        //We first take the composite 
        this.control = cp.getControl();
        //Do some setup
        this.setPreferredSize(new Dimension(800,600));
        //and at ourselfes to the circel of people who want
        //to be informed about changes
        this.control.addObserver(this);
        this.control.addOperationListener(this);
        //then we add some listeners to watch mouseaction
        addListeners ();
    }

    //To get painted is easy
    public void paint (final Graphics g) {
        
        //we just let the control do what ever it does
        control.drawYourself (g);
        //And just draw the line which we are currently drawing
        drawCurrentLine(g);       
    }

    private void drawCurrentLine (final Graphics g) {
        //We maintain moins of origin and destiny
        if (startPoint != null && currentPoint != null) {
            //between them we draw aline
            g.drawLine(startPoint.x, startPoint.y, currentPoint.x, currentPoint.y);
        }
    }

    private void addListeners () {
        //We watchout for mousemotion
        this.addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseDragged (final MouseEvent me) {
                //If the mouse is dragged there are two possibilitys 
                //shift is down 
                if ((me.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
                    //Then we want to move the component 
                    //So we are not connecting
                    connecting = false;

                    //So we calculate the difference betweeen the point we 
                    //this drag started (the mouse was pressed see there)
                    //And the current mousepositon
                    final int dx = me.getX() - startPoint.x;
                    final int dy = me.getY() - startPoint.y;

                    //And tell the control to do what it has to do to move the operation
                    //if there is on but thats none of our concern
                    control.moveObjectAtBy(startPoint, dx, dy);

                    //And change the start of this drag
                    startPoint = me.getPoint();
                } else {
                    //If shift is up we are trying to connect
                    if (control.connectAllowed(startPoint)) {
                        //if the controll allows us 
                        connecting = true;
                        //we set the currentPoint (which will draw a line
                        //bewtween start and current point (see above)

                        currentPoint = me.getPoint();
                    }
                }
                //In any case we need to repaint
                repaint ();
            }
        });

        //Now for mouseevents (why are these distingushed in java?)
        this.addMouseListener(new MouseAdapter () {
            //if the mouse is pressed
            public void mousePressed (final MouseEvent me) {
                //we set the startpoint
                startPoint = me.getPoint();
                
                //And set a component we might have hit selected
                control.clicked(me.getPoint());
                
                //And repaint
                repaint ();
            }
            public void mouseReleased (final MouseEvent me) {
                //If we were connecting 
                if (connecting) {
                    //we tell the control to try to connect the
                    //componennt at the start and endpoint of 
                    //the drag
                    control.connect (startPoint, currentPoint);
                }

                //And set start and end to null
                //to stop drawing a line
                startPoint = null;
                currentPoint = null;

                //And we definitly aren't try to connect
                //Anything anymore
                connecting = false;
                //And repaint
                repaint  ();
            }
            public void mouseClicked (final MouseEvent me) {
                if ( (me.getClickCount() & 1) == 0) {
                    control.doubleClicked(me.getPoint(), _this);
                    repaint ();
                }
            }
        });
    }

    //If the contoll tells us that it has changed
    public void update (final Observable observable, final Object arg) {
        //We look if it is our control
        if (observable == this.control) {
            if (! (arg instanceof Boolean)) {
            
            //we need to repaint
            
            //Now we check if we moved a component (partially) outside of the current viewport
            final Dimension currentDimension = this.getPreferredSize();
            final Dimension newDimension = new Dimension (Math.max(currentDimension.width, this.control.getVerticalSize()), 
                    Math.max(currentDimension.height, this.control.getHorizontalSize()));
            //And change the preferred dimension so that we could enable scrolling (just in case)
            this.setPreferredSize(newDimension);
            //This is needed by Java to except the new dimension settings
            this.revalidate();
            repaint ();
            } else {
                paintImmediately(this.getBounds());
            }
        }
    }

    //Whatever happens to the inputs/outputs or operations 
    //We surely need to repaint
    public void inputAdded(final FunctionInputOperation fio) {
        repaint ();
    }

    public void inputRemoved(final FunctionInputOperation fio) {
        repaint ();
    }

    public void outputAdded(final FunctionOutputOperation foo) {
        repaint ();
    }

    public void outputRemoved(final FunctionOutputOperation foo) {
        repaint ();
    }
    
    public void operationAdded (final Operation op) {
        repaint ();
    }
    
    public void operationRemoved (final Operation op) {
        repaint ();
    }
}
